package com.xxx.springboot.util;

import java.util.Collections;
import java.util.UUID;

import com.xxx.springboot.service.impl.SpringUtil;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;

/**
 * redis分布式同步锁
 * 同步处理完后可以手动及时释放锁也可以利用锁的超时失效时间自动释放锁
 */
public class RedisSynLock {

	private static final String LOCK_SUFFIX = "_lock";

	private static final Integer DEFAULT_EXPIRETIME = 2;// 默认锁超时失效时间2秒

	private String lockKey;

	private Integer expireTime;

	String lockValue = UUID.randomUUID().toString();

	/**
	 * 加锁脚本
	 */
	// if redis.call("exists",KEYS[1]) == 0 then
	// local lockSrc = redis.call("setex",KEYS[1],unpack(ARGV))
	// if lockSrc then
	// return "1"
	// end
	// return "0"
	// end
	// return "-1"
	private static final String LOCK_SCRIPT_STRING = "if redis.call(\"exists\",KEYS[1]) == 0 then local lockSrc = redis.call(\"setex\",KEYS[1],unpack(ARGV)) if lockSrc then return 1 end return 0 end return -1";

	/**
	 * 释放锁脚本
	 */
	// if redis.call("get",KEYS[1]) == ARGV[1] then
	// local lockRelease = redis.call("del",KEYS[1])
	// if lockRelease then
	// return "1"
	// end
	// return "0"
	// end
	// return "-1"
	private static final String UN_LOCK_SCRIPT_STRING = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then local lockRelease = redis.call(\"del\",KEYS[1]) if lockRelease then return 1 end return 0 end return -1";

	/**
	 * 加锁脚本
	 */
	private static final RedisScript<Long> LOCK_SCRIPT = new DefaultRedisScript<>(LOCK_SCRIPT_STRING, Long.class);

	/**
	 * 释放锁的脚本
	 */
	private static final RedisScript<Long> UN_LOCK_SCRIPT = new DefaultRedisScript<>(UN_LOCK_SCRIPT_STRING, Long.class);

	public RedisSynLock(String lockKey, int expireTime) {
		this.lockKey = lockKey + LOCK_SUFFIX;
		this.expireTime = expireTime;
	}

	public RedisSynLock(String lockKey) {
		this(lockKey + LOCK_SUFFIX, DEFAULT_EXPIRETIME);
	}

	/**
	 * 尝试获取分布式锁
	 * @return 是否获取成功
	 */
	public boolean trylock() {
		Long result = lockExecute();
		return result == 1;
	}

	/**
	 * 必须获取分布式锁（阻塞式）
	 * @return
	 */
	public boolean needlock() {
		boolean lock = false;
		while (!lock) {
			lock = trylock();
			if (!lock) {
				sleep(50);
			}
		}
		return lock;
	}

	private Long lockExecute() {
		StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
		Long result = stringRedisTemplate.execute(LOCK_SCRIPT, Collections.singletonList(lockKey), expireTime + "", lockValue);
		return result;
	}

	/**
	 * 手动释放本次请求（该requestId）的锁
	 * @return
	 */
	public boolean unlock() {
		StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
		Long result = stringRedisTemplate.execute(UN_LOCK_SCRIPT, Collections.singletonList(lockKey), lockValue);
		return result == 1;
	}

	private void sleep(long millis) {
		try {
			Thread.sleep(millis);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

}
